package com.elinshaw.classloader;

import java.io.ByteArrayOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 自定义类加载器
 * 每一个类加载器实例都有自己的命名空间
 * 命名空间由该类加载器所有父类加载器构成
 * 每个类加载器中同一个class独一无二
 * 使用不同类加载器加载加载同一个class则在堆和方法区产生多个class对象
 * <p>
 * 类加载过程中所有参与的类加载器即使没有亲自加载过该类也会被标识为该类的初始化类加载器
 * -verbose:class
 */
public class MyClassLoader extends ClassLoader {

    private final Path classDir;

    private final static Path DEFAULT_CLASS_DIR = Paths.get(".", "/src/main/java/").toAbsolutePath();

    public MyClassLoader() {
        super();
        this.classDir = DEFAULT_CLASS_DIR;
    }

    public MyClassLoader(String classDir, ClassLoader parent) {
        super(parent);
        this.classDir = Paths.get(classDir).toAbsolutePath();
    }

    public MyClassLoader(String classDir) {
        super();
        this.classDir = Paths.get(classDir).toAbsolutePath();
    }

    /**
     * 破坏双亲委派模式例如（使用spi-jdbc）
     * 线程上下文类加载器（根加载器委托子类加载器加载第三方厂商的SPI实现）
     *
     * @param name
     * @param resolve
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        return super.loadClass(name, resolve);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //读取class二进制
        byte[] classBytes = this.readClassBytes(name);
        if (null == classBytes && classBytes.length == 0) {
            throw new ClassNotFoundException(" can not load the class " + name);
        }
        //定义class
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }


    /**
     * 将class读入内存
     *
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    private byte[] readClassBytes(String name) throws ClassNotFoundException {
        String classPath = name.replace(".", "/");
        Path classFullPath = classDir.resolve(Paths.get(classPath + ".class"));
        if (!classFullPath.toFile().exists()) {
            throw new ClassNotFoundException("The class " + name + " not  found");
        }
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            Files.copy(classFullPath, byteArrayOutputStream);
            return byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException(" load the class " + name + "occur error  found", e);
        }

    }

    @Override
    public String toString() {
        return "MyClassLoader{" +
                "classDir=" + classDir +
                '}';
    }
}
